//! Intrinsics that represent helpers that implement async calls

use crate::intrinsics::Intrinsic;
use crate::intrinsics::component::ComponentIntrinsic;
use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
use crate::source::Source;

/// This enum contains intrinsics that implement async calls
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub enum HostIntrinsic {
    /// Intrinsic used by the host to prepare trampoline calls
    ///
    /// # Host Intrinsic implementation function
    ///
    /// The function that implements this intrinsic has the following definition:
    ///
    /// ```ts
    /// type i32 = number;
    /// function prepareCall(memoryIdx: i32): boolean;
    /// ```
    ///
    PrepareCall,

    /// Intrinsic used by the host to signal the start of an async-lowered call
    ///
    /// This intrinsic signals the start of an Async call emitted by modules generated
    /// by wasmtime's Fused Adapter Compiler of Trampolines (FACT)
    ///
    /// This call indicates that an async-lowered import function is being called
    /// (that has either been async lifted or not on the callee side)
    ///
    /// This intrinsic returns a combination of the initial call status and optionally
    /// the handle to a waitable that should be awaited until it's time to (if necessary),
    /// packed into the same u64.
    ///
    /// # Host Intrinsic implementation function
    ///
    /// The function that implements this intrinsic has the following definition:
    ///
    /// ```ts
    /// type i32 = number;
    /// type u64 = number;
    /// function asyncStartCall(callbackIdx: i32, postReturnIdx: i32): u64;
    /// ```
    ///
    AsyncStartCall,

    /// Start of an sync call emitted by modules generated by wasmtime's
    /// Fused Adapter Compiler of Trampolines (FACT)
    ///
    /// This call maps indicates a trampoline for a sync-lowered import
    /// of an async-lifted export, meaning that the *calling* component
    /// has sync lowered, but the callee has async lifted
    ///
    /// this intrinsic signals the start of an sync-lowered call
    /// of an async-lifted export.
    ///
    /// # Host Intrinsic implementation function
    ///
    /// The function that implements this intrinsic has the following definition:
    ///
    /// ```ts
    /// type i32 = number;
    /// function syncStartCall(callbackIdx: i32): boolean;
    /// ```
    ///
    SyncStartCall,
}

impl HostIntrinsic {
    /// Retrieve dependencies for this intrinsic
    pub fn deps() -> &'static [&'static Intrinsic] {
        &[]
    }

    /// Retrieve global names for this intrinsic
    pub fn get_global_names() -> impl IntoIterator<Item = &'static str> {
        ["syncStartCall", "asyncStartCall", "prepareCall"]
    }

    /// Get the name for the intrinsic
    pub fn name(&self) -> &'static str {
        match self {
            Self::PrepareCall => "prepareCall",
            Self::AsyncStartCall => "asyncStartCall",
            Self::SyncStartCall => "syncStartCall",
        }
    }

    /// Render an intrinsic to a string
    pub fn render(&self, output: &mut Source) {
        match self {
            // PrepareCall is called before an async-lowered import (from the host or another component)
            // is called from inside a component.
            //
            // It's primary function is to set up a Subtask which will be used by the callee.
            // see: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md#structured-concurrency
            //
            // NOTE: it's possible for PrepareCall to be combined with AsyncStartCall
            // in a future component model release.
            //
            Self::PrepareCall => {
                let debug_log_fn = Intrinsic::DebugLog.name();
                let prepare_call_fn = Self::PrepareCall.name();
                let current_async_task_id_globals =
                    AsyncTaskIntrinsic::GlobalAsyncCurrentTaskIds.name();
                let current_component_idx_globals =
                    AsyncTaskIntrinsic::GlobalAsyncCurrentComponentIdxs.name();
                let get_or_create_async_state_fn =
                    Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState).name();
                let current_task_get_fn =
                    Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask).name();

                output.push_str(&format!(
                    "
                    function {prepare_call_fn}(memoryIdx) {{
                        {debug_log_fn}('[{prepare_call_fn}()] args', {{ memoryIdx }});

                        const taskMeta = {current_task_get_fn}({current_component_idx_globals}.at(-1), {current_async_task_id_globals}.at(-1));
                        if (!taskMeta) {{ throw new Error('invalid/missing current async task meta during prepare call'); }}

                        const task = taskMeta.task;
                        if (!task) {{ throw new Error('unexpectedly missing task in task meta during prepare call'); }}

                        const state = {get_or_create_async_state_fn}(task.componentIdx());
                        if (!state) {{
                            throw new Error('invalid/missing async state for component instance [' + componentInstanceID + ']');
                        }}

                        const subtask = task.createSubtask({{
                            memoryIdx,
                        }});

                    }}
                "
                ));
            }

            // AsyncStartCall is called just before an async-lowered import (host/another component),
            // is called from inside a component.
            //
            // It's primary function is to actually start the Subtask that should have been prepared at
            // this point via PrepareCall, and to return the intial code made available by the subtask
            // which will prompt the caller to take action, depending on the output.
            //
            // For example, if the async lowered import is called and it immediately returns RETURNED,
            // Then the caller will know that the callee has completed and can act accordingly.
            //
            Self::AsyncStartCall => {
                let debug_log_fn = Intrinsic::DebugLog.name();
                let async_start_call_fn = Self::AsyncStartCall.name();
                let current_async_task_id_globals =
                    AsyncTaskIntrinsic::GlobalAsyncCurrentTaskIds.name();
                let current_component_idx_globals =
                    AsyncTaskIntrinsic::GlobalAsyncCurrentComponentIdxs.name();
                let current_task_get_fn =
                    Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask).name();

                output.push_str(&format!("
                    function {async_start_call_fn}(callbackIdx, postReturnIdx) {{
                        {debug_log_fn}('[{async_start_call_fn}()] args', {{ callbackIdx, postReturnIdx }});

                        const taskMeta = {current_task_get_fn}({current_component_idx_globals}.at(-1), {current_async_task_id_globals}.at(-1));
                        if (!taskMeta) {{ throw new Error('invalid/missing current async task meta during prepare call'); }}

                        const task = taskMeta.task;
                        if (!task) {{ throw new Error('unexpectedly missing task in task meta during prepare call'); }}

                        const subtask = task.currentSubtask();
                        if (!subtask) {{ throw new Error('invalid/missing subtask during async start call'); }}

                        return Number(subtask.waitableRep()) << 4 | subtask.getStateNumber();
                    }}
                "));
            }

            // TODO: implement
            Self::SyncStartCall => {
                let debug_log_fn = Intrinsic::DebugLog.name();
                let sync_start_call_fn = Self::SyncStartCall.name();
                output.push_str(&format!(
                    "
                    function {sync_start_call_fn}(callbackIdx) {{
                        {debug_log_fn}('[{sync_start_call_fn}()] args', {{ callbackIdx }});
                    }}
                "
                ));
            }
        }
    }
}
